﻿using System;
using System.Collections.Generic;
using Heaven.Engine.Concurrency;
using Heaven.Graphics;
using Heaven.Texturing;
using Heaven.Mathematics;

namespace Heaven.Engine
{
    /// <summary>
    /// Represents a collection of properies
    /// </summary>
    public class PropertyCollection : IEnumerable<NamedProperty>
    {
        #region Events
        
        /*/// <summary>
        /// Occurs when a property has been added
        /// </summary>
        public event EventHandler<NamedPropertyEventArgs> Added;

        /// <summary>
        /// Occurs when a property has been removed
        /// </summary>
        public event EventHandler<NamedPropertyEventArgs> Removed;*/

        // Weak events
        WeakMulticastDelegate invalidated = new WeakMulticastDelegate();

        /// <summary>
        /// Occurs when something has been changed
        /// </summary>
        public event EventHandler Invalidated
        {
            add { invalidated.Add(value); }
            remove { invalidated.Remove(value); }
        }

        #endregion

        #region Fields

        // Dictionary with properties
        Dictionary<string, NamedProperty> dictionary = new Dictionary<string, NamedProperty>(2);
        // Shadow object
        Dictionary<string, NamedProperty> shadow = new Dictionary<string, NamedProperty>(2);
        DeferredActions actions = new DeferredActions();

        #endregion

        #region Properties

        /// <summary>
        /// Gets a number of properties in the collection
        /// </summary>
        public int Count
        {
            get { return dictionary.Count; }
        }

        /// <summary>
        /// Gets or sets an property by its name 
        /// (if a property with the same name exists, 
        /// that property will be removed and 
        /// the given one will be added)
        /// </summary>
        /// <param name="name">Name of the property</param>
        /// <returns>Property</returns>
        public IProperty this[string name]
        {
            get { return dictionary[name].Property; }
            set
            {
                if (dictionary.ContainsKey(name))
                {
                    Remove(name);
                }

                Add(name, value);
            }
        }

        #endregion

        #region Initialization

        /// <summary>
        /// Constructor
        /// </summary>
        public PropertyCollection()
        {

        }

        #endregion
        
        #region Methods

        void Add(string name, IProperty property)
        {
            NamedProperty namedProperty = new NamedProperty(name, property);
            dictionary.Add(name, namedProperty);
            actions.Add(AddDeferred, name, namedProperty);
            property.Invalidated += new EventHandler(OnPropertyValueChanged);
            //if (Added != null) Added(this, new NamedPropertyEventArgs(namedProperty));
            
            invalidated.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Determines whether the property with 
        /// the given name in this collection
        /// </summary>
        /// <param name="name">Name of the given property</param>
        /// <returns>True, if the property exists</returns>
        public bool Contains(string name)
        {
            return dictionary.ContainsKey(name);
        }

        /// <summary>
        /// Removes the given property
        /// </summary>
        /// <param name="name">Name of the given property</param>
        public void Remove(string name)
        {
            NamedProperty namedProperty = dictionary[name];
            namedProperty.Property.Invalidated -= new EventHandler(OnPropertyValueChanged);
            dictionary.Remove(name);
            actions.Add(RemoveDeferred, name);
            
            //if (Removed != null) Removed(this, new NamedPropertyEventArgs(namedProperty));
            invalidated.Invoke(this, EventArgs.Empty);
        }

        /// <summary>
        /// Gets name of the given property (complexity O(n))
        /// </summary>
        /// <param name="property">Property</param>
        /// <returns>Name of the given property</returns>
        public string GetNameOf(IProperty property)
        {
            foreach (NamedProperty namedProperty in dictionary.Values)
            {
                if (namedProperty.Property == property) return namedProperty.Name;
            }
            throw new ArgumentException("There is no the given property in the collection");
        }
        
        void OnPropertyValueChanged(object sender, EventArgs e)
        {
            invalidated.Invoke(this, EventArgs.Empty);
        }

        #endregion

        #region Shadow Actions

        void AddDeferred(object[] input)
        {
            shadow.Add((string)input[0], (NamedProperty)input[1]);
        }

        void RemoveDeferred(object[] input)
        {
            shadow.Remove((string)input[0]);
        }

        #endregion

        #region Enumeration

        /// <summary>
        /// Uploads properties to the graphics device
        /// </summary>
        /// <param name="device">Graphics device</param>
        /// <param name="shader">Shader</param>
        internal void UploadProperties(IDevice device, Shader shader)
        {
            actions.Perform();

            foreach (NamedProperty property in shadow.Values)
            {
                object value = property.Property.Value;
                if (value is double)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(double)value);
                }
                else if (value is Color)
                {
                    device.SetShaderParameter(shader, property.Name, (Color)value);
                }
                else if (value is Vector4)
                {
                    device.SetShaderParameter(shader, property.Name, (Vector4)value);
                }
                else if (value is Vector3)
                {
                    device.SetShaderParameter(shader, property.Name, (Vector3)value);
                }
                else if (value is Matrix4)
                {
                    device.SetShaderParameter(shader, property.Name, (Matrix4)value);
                }
                else if (value is Texture)
                {
                    device.SetShaderParameter(shader, property.Name, (Texture)value);
                }
                else if (value is float)
                {
                    device.SetShaderParameter(shader, property.Name, (float)value);
                }
                else if (value is decimal)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(decimal)value);
                }
                else if (value is int)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(int)value);
                }
                else if (value is long)
                {
                    device.SetShaderParameter(shader, property.Name, (float)(long)value);
                }
            }
        }

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return dictionary.Values.GetEnumerator();
        }
        
        /// <summary>
        /// Gets an enumerator
        /// </summary>
        /// <returns>Enumerator</returns>
        public IEnumerator<NamedProperty> GetEnumerator()
        {
            return dictionary.Values.GetEnumerator();
        }

        #endregion
    }

    /// <summary>
    /// Represents a named property
    /// </summary>
    public class NamedProperty
    {
        /// <summary>
        /// Name of the property
        /// </summary>
        public readonly string Name;

        /// <summary>
        /// The property
        /// </summary>
        public readonly IProperty Property;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="name">Name of the property</param>
        /// <param name="property">The property</param>
        public NamedProperty(string name, IProperty property)
        {
            Name = name;
            Property = property;
        }
    }

    /// <summary>
    /// Represent event's arguments with named property
    /// </summary>
    public class NamedPropertyEventArgs : EventArgs
    {
        /// <summary>
        /// Property
        /// </summary>
        public readonly NamedProperty NamedProperty;

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="namedProperty">Property</param>
        public NamedPropertyEventArgs(NamedProperty namedProperty)
        {
            NamedProperty = namedProperty;
        }
    }

    /// <summary>
    /// Shadow class for property collection
    /// </summary>
    class PropertyCollectionShadow : IEnumerable<NamedProperty>
    {
        #region Fields

        // Dictionary with properties
        Dictionary<string, NamedProperty> dictionary = new Dictionary<string, NamedProperty>(2);
        // Deferred actions
        DeferredActions actions = new DeferredActions();

        #endregion

        #region Methods

        /// <summary>
        /// Adds a property
        /// </summary>
        /// <param name="name"></param>
        /// <param name="property"></param>
        public void Add(string name, IProperty property)
        {
            actions.Add(AddAction, name, property);
        }

        /// <summary>
        /// Adds a property
        /// </summary>
        /// <param name="name"></param>
        /// <param name="property"></param>
        public void Remove(string name)
        {
            actions.Add(RemoveAction, name);
        }

        #endregion

        #region Enumeration

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return dictionary.Values.GetEnumerator();
        }

        /// <summary>
        /// Gets an enumerator
        /// </summary>
        /// <returns>Enumerator</returns>
        public IEnumerator<NamedProperty> GetEnumerator()
        {
            return dictionary.Values.GetEnumerator();
        }

        #endregion

        #region Deffered methods

        void AddAction(object[] parameters)
        {
            dictionary.Add((string)parameters[0], new NamedProperty((string)parameters[0], (IProperty)parameters[1]));
        }

        void RemoveAction(object[] parameters)
        {
            dictionary.Remove((string)parameters[0]);
        }

        #endregion
    }
}
